Skip to content

Add Tailor: learns how you like the assistant to talk - #389

Merged
uzair401 merged 3 commits into
openhome-dev:devfrom
hassan1731996:add-tailor
Nov 11, 2026
Merged

uzair401 merged 3 commits into
openhome-dev:devfrom
hassan1731996:add-tailor

Conversation

@hassan1731996

@hassan1731996 hassan1731996 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What does this Ability do?

Tailor makes the assistant learn how each user likes it to talk. A background daemon spots feedback on the assistant's own replies ("too long", "just give me the number", "stop asking if I want more", or the user re-asking a question the reply missed). It turns that feedback into short style rules in tailor_style.md, which the main Agent follows within a couple of minutes. The voice skill reads the rules back one at a time, explains where each came from, and lets the user change, add, pause or reset them.

No existing ability changes how the assistant itself talks. Memory Mirror corrects facts about the user. Conversation Insights Coach coaches the user's own speech. Jarvis Prime, Social Memory and Decision Journal remember things the user mentions. The README covers the differences.

Suggested Trigger Words

  • tailor
  • how do you talk to me
  • what have you learned about how I like you to talk
  • my style rules
  • why do you talk like that

Learning needs no trigger; background.py runs while the session is connected.

Type

  • New community Ability
  • Improvement to existing Ability
  • Bug fix
  • Documentation update

External APIs

  • No external APIs
  • Uses external API(s):

Testing

  • Tested in OpenHome Live Editor
  • All exit paths tested (said "stop", "exit", etc.)
  • Error scenarios tested (API down, bad input, etc.)

Tested in the OpenHome Live Editor and working, including exit paths and error scenarios. These (storage failures, malformed LLM output, injected rules) are also covered offline with a mock CapabilityWorker (43 scripted cases), listed under "Anything else?" below.

Checklist

  • Files are in community/tailor/
  • main.py follows SDK pattern (extends MatchingCapability, has register_capability + call)
  • README.md included with description, suggested triggers, and setup
  • resume_normal_flow() called on every exit path
  • No print() — using editor_logging_handler
  • No hardcoded API keys — using placeholders
  • No blocked imports (redis, user_config)
  • No asyncio.sleep() or asyncio.create_task() — using session_tasks
  • Error handling on all external calls
  • Tested in OpenHome Live Editor

Anything else?

Design notes, following earlier review feedback

  • Triggers: no does_match(); they live in the dashboard.
  • Background daemon: background_daemon_mode is set before CapabilityWorker(self), there is no resume_normal_flow() in background.py, and it polls every 90 s. Most turns never reach the LLM (a cheap pre-filter, and at most 3 calls per poll).
  • State: saved in Context Storage with create_key, then update_key, checking success. Reads unwrap ["value"]. A failed read never lets a save overwrite the user's rules. A missing key starts fresh, while a storage error makes both the skill and the daemon refuse to write.
  • Busy flag: a separate tailor_busy key stops the daemon learning from the Tailor conversation itself, and it can never overwrite the rules.
  • Strict merges: the LLM's "same rule" or "contradicts rule" links are only honoured when topic and rule type agree in code, so unrelated rules are never overwritten.
  • Confirmations: an LLM classifier with a whole-utterance fast path. Users can answer with a change ("no, only for news"). Exit is checked at every prompt, including nested ones, using whole-phrase matches.
  • Speech: one rule per turn, no raw dates ("about a week ago"), confirmations of 30 words or fewer, and the session stays open after each action.
  • Profile reuse: the name and up to two existing communication-preference notes come from user_profile.md / user_summary.md, read-only, so the user isn't asked for them.
  • Safety: rules are limited to style categories (length, tone, follow-ups, units, depth and so on) and are checked twice: by the prompt and by a regex in code. Transcript text is fenced as untrusted data. No transcripts are stored, and logs hold only intents, counts and rule ids.
  • Limits: at most 8 rules, an injected file under 180 words, and learned rules expire after 30 days without reinforcement.

Offline test coverage (43 cases)

  • explicit feedback becomes a rule; a single implicit signal stays pending, and a repeat promotes it
  • injected and non-style rules are rejected
  • contradictions replace a rule and matches reinforce it
  • cross-topic links are ignored
  • the 8-rule and 180-word caps hold, and expiry works
  • pause, the busy flag, and storage errors all leave saved rules untouched
  • browsing, why, remove, change-via-confirmation, add, reset and pause in the skill
  • exit at nested prompts and idle timeout
  • validate_ability.py and flake8 (CI settings) pass locally

A background daemon spots feedback on the assistant's own replies
("too long", "just the number", a re-asked question) and turns it into
short style rules in tailor_style.md, which the main Agent follows.
The voice skill reads the rules back one at a time, explains where each
came from, and lets the user change, add, pause or reset them.
@hassan1731996
hassan1731996 requested a review from a team as a code owner October 25, 2026 09:35
@github-actions

Copy link
Copy Markdown
Contributor

✅ Community PR Path Check — Passed

All changed files are inside the community/ folder. Looks good!

@github-actions

Copy link
Copy Markdown
Contributor

🔀 Branch Merge Check

PR direction: add-tailor → dev

✅ Passed — add-tailor → dev is a valid merge direction

@github-actions github-actions Bot added the community-ability Community-contributed ability label Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Ability Validation Passed

📋 Validating: community/tailor
  ✅ All checks passed!

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Lint Results

✅ __init__.py — Empty as expected

Files linted: community/tailor/background.py community/tailor/main.py

✅ Flake8 — Passed

✅ All checks passed!

- Remove the getattr fallback; history messages are always dicts.
- Track new messages by matching the last few already seen instead of a
  list position, so feedback isn't skipped or counted twice once the
  conversation gets long.
- Don't save a fresh empty state just for the daily cleanup, and skip a
  poll if saved rules that existed earlier come back missing, so a failed
  read can't wipe the rules.
- Keep the busy flag set briefly after Tailor closes, so its own
  conversation ("forget the too long one") isn't learned as feedback.
@uzair401

Copy link
Copy Markdown
Contributor

Hello @hassan1731996, thanks for this, really nice idea. Letting people hear why the assistant talks the way it does, and correct it, feels genuinely useful. I reviewed it closely and pushed a few fixes directly to this branch:

Push check: background.py used getattr, which the OpenHome push check rejects, so the ability couldn't be deployed. History messages are always dicts, so that fallback is removed.

Learning in longer conversations: the daemon remembered its place in the conversation by list position. History only keeps recent messages, so once a conversation got long, its length stayed the same while new messages came in. New feedback was skipped, and sometimes older feedback was counted again and promoted to a rule. It now finds its place by matching the last few messages it already saw.

Saved rules: if reading the saved rules failed for a moment, the daemon started from an empty state, and the daily cleanup then saved that over the real rules. A fresh empty state is no longer saved for the cleanup alone, and a poll is skipped if rules that existed earlier come back missing.

Tailor's own conversation: the busy flag cleared the moment Tailor closed. A short session was then scanned as normal conversation, so "forget the too long one" could re-learn the rule that was just deleted. The flag now stays set briefly after closing.

A few smaller things you could look at in a follow-up:

  • The skill has the same problem when reading saved rules fails. It says "I haven't learned anything yet", and adding a rule then saves over the real ones.
  • describe() crashes on an empty rule text, and dates use UTC rather than the user's timezone.
  • Feedback beyond the 3 LLM calls per poll is dropped rather than picked up next time.

@uzair401
uzair401 merged commit 79f85a8 into openhome-dev:dev Nov 11, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-ability Community-contributed ability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants